/*
 * Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.jmx.mbeanserver;

import static com.sun.jmx.mbeanserver.Util.*;

import java.util.Iterator;
import java.util.Set;

import javax.management.InstanceAlreadyExistsException;
import javax.management.JMX;
import javax.management.MBeanServer;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;

/**
 * Base class for MXBeans.
 *
 * @since 1.6
 */
public class MXBeanSupport extends MBeanSupport<ConvertingMethod> {

  /**
   * <p>Construct an MXBean that wraps the given resource using the
   * given MXBean interface.</p>
   *
   * @param resource the underlying resource for the new MXBean.
   * @param mxbeanInterface the interface to be used to determine the MXBean's management
   * interface.
   * @param <T> a type parameter that allows the compiler to check that {@code resource} implements
   * {@code mxbeanInterface}, provided that {@code mxbeanInterface} is a class constant like {@code
   * SomeMXBean.class}.
   * @throws IllegalArgumentException if {@code resource} is null or if it does not implement the
   * class {@code mxbeanInterface} or if that class is not a valid MXBean interface.
   */
  public <T> MXBeanSupport(T resource, Class<T> mxbeanInterface)
      throws NotCompliantMBeanException {
    super(resource, mxbeanInterface);
  }

  @Override
  MBeanIntrospector<ConvertingMethod> getMBeanIntrospector() {
    return MXBeanIntrospector.getInstance();
  }

  @Override
  Object getCookie() {
    return mxbeanLookup;
  }

  static <T> Class<? super T> findMXBeanInterface(Class<T> resourceClass) {
    if (resourceClass == null) {
      throw new IllegalArgumentException("Null resource class");
    }
    final Set<Class<?>> intfs = transitiveInterfaces(resourceClass);
    final Set<Class<?>> candidates = newSet();
    for (Class<?> intf : intfs) {
      if (JMX.isMXBeanInterface(intf)) {
        candidates.add(intf);
      }
    }
    reduce:
    while (candidates.size() > 1) {
      for (Class<?> intf : candidates) {
        for (Iterator<Class<?>> it = candidates.iterator(); it.hasNext();
            ) {
          final Class<?> intf2 = it.next();
          if (intf != intf2 && intf2.isAssignableFrom(intf)) {
            it.remove();
            continue reduce;
          }
        }
      }
      final String msg =
          "Class " + resourceClass.getName() + " implements more than " +
              "one MXBean interface: " + candidates;
      throw new IllegalArgumentException(msg);
    }
    if (candidates.iterator().hasNext()) {
      return Util.cast(candidates.iterator().next());
    } else {
      final String msg =
          "Class " + resourceClass.getName() +
              " is not a JMX compliant MXBean";
      throw new IllegalArgumentException(msg);
    }
  }

  /* Return all interfaces inherited by this class, directly or
   * indirectly through the parent class and interfaces.
   */
  private static Set<Class<?>> transitiveInterfaces(Class<?> c) {
    Set<Class<?>> set = newSet();
    transitiveInterfaces(c, set);
    return set;
  }

  private static void transitiveInterfaces(Class<?> c, Set<Class<?>> intfs) {
    if (c == null) {
      return;
    }
    if (c.isInterface()) {
      intfs.add(c);
    }
    transitiveInterfaces(c.getSuperclass(), intfs);
    for (Class<?> sup : c.getInterfaces()) {
      transitiveInterfaces(sup, intfs);
    }
  }

  /*
   * The sequence of events for tracking inter-MXBean references is
   * relatively complicated.  We use the magical preRegister2 method
   * which the MBeanServer knows about.  The steps during registration
   * are:
   * (1) Call user preRegister, if any.  If exception, abandon.
   * (2) Call preRegister2 and hence this register method.  If exception,
   * call postRegister(false) and abandon.
   * (3) Try to register the MBean.  If exception, call registerFailed()
   * which will call the unregister method.  (Also call postRegister(false).)
   * (4) If we get this far, we can call postRegister(true).
   *
   * When we are wrapped in an instance of javax.management.StandardMBean,
   * things are simpler.  That class calls this method from its preRegister,
   * and propagates any exception.  There is no user preRegister in this case.
   * If this method succeeds but registration subsequently fails,
   * StandardMBean calls unregister from its postRegister(false) method.
   */
  @Override
  public void register(MBeanServer server, ObjectName name)
      throws InstanceAlreadyExistsException {
    if (name == null) {
      throw new IllegalArgumentException("Null object name");
    }
    // eventually we could have some logic to supply a default name

    synchronized (lock) {
      this.mxbeanLookup = MXBeanLookup.lookupFor(server);
      this.mxbeanLookup.addReference(name, getResource());
      this.objectName = name;
    }
  }

  @Override
  public void unregister() {
    synchronized (lock) {
      if (mxbeanLookup != null) {
        if (mxbeanLookup.removeReference(objectName, getResource())) {
          objectName = null;
        }
      }
    }
  }

  private final Object lock = new Object(); // for mxbeanLookup and objectName

  private MXBeanLookup mxbeanLookup;
  private ObjectName objectName;
}
